#!/usr/bin/env luajit
-- -*- lua -*-
--
-- Copyright (C) 2014-2020 Markus Klotzbuecher <mk@mkio.de>
--
-- SPDX-License-Identifier: BSD-3-Clause
--

local ubx = require("ubx")
local bd = require("blockdiagram")
local utils = require("utils")
local strict = require("strict")

local res,lfs = _G.pcall(_G.require,'lfs')

if not res then
   print("error: ubx-mq requires lua-filesystem"); os.exit(1)
end

local MQPATH="/dev/mqueue"

local ts = tostring
local fmt = string.format

local nd
local verbose=false

if not res then
   print("error: ubx-mq requires LuaFileSystem")
   os.exit(1)
end

local function usage()
   print([[
usage: ubx-mq <command> [<args>]
commands:
   list               list all ubx mqueues
   read  MQ-ID        read and output data of mqueue MQ-ID
   write MQ-ID VALUE  write VALUE to mq MQ-ID
      -r RATE             write at rate RATE instead of just once

global parameters:
   -p m1,m2     list of modules to preload
                (stdtypes is loaded by default)
   -v           be verbose
   -h           show help
]])
end

--- mqueues_get
-- reads the mqueue directory and parses the ubx mqueues
-- into tables with mq_id = { hashstr=..., data_len=... }
-- @return a table of ubx_mqueues entries
local function mqueues_get()
   local res = {}

   for file in lfs.dir(MQPATH) do
      local hashstr, data_len, mq_id = string.match(file, "ubx_(%w+)_(%d+)_(.+)")
      if hashstr and data_len and mq_id then
	 res[#res+1] = { mq_id=mq_id, hashstr=hashstr, data_len=tonumber(data_len) }
      end
   end
   return res
end

--- Create an initialize a new mq
-- @param mqtab table entry of mqueues_get result
-- @param preloads modules to preload
-- @return mq message queue
-- @return sample a sampe value of the queue
local function newmq(mqtab, preloads)
   nd = ubx.node_create("ubx-mq", { loglevel=8})

   ubx.load_module(nd, "stdtypes")
   ubx.load_module(nd, "mqueue")

   for _,m in ipairs(preloads) do
      ubx.load_module(nd, m)
   end

   local typ = ubx.type_get_by_hashstr(nd, mqtab.hashstr)

   if typ == nil then
      print(fmt("no type found for %s, use -p option to preload types", mqtab.hashstr))
      os.exit(1)
   end

   local sample = ubx.__data_alloc(typ, mqtab.data_len)

   ubx.block_create(nd, "ubx/mqueue", "mq", {
		       mq_id = mqtab.mq_id,
		       type_name = ubx.safe_tostr(typ.name),
		       data_len = mqtab.data_len,
		       buffer_len = 32,
		       unlink=0,
		       blocking = 1 } )

   local mq = nd:b("mq")

   if ubx.block_tostate(mq, 'active') ~= 0 then
      print("failed to activate mq")
      os.exit(1)
   end

   return mq, sample
end

--- read from the given mqueue and print the result
-- @param mqtab message queue id
-- @param preloads modules to preload
-- @return never returns
local function read(mqtab, preloads)
   local mq, sample = newmq(mqtab, preloads)
   local cnt=0

   while true do
      local ret = mq:read(sample)
      if ret > 0 then
	 if verbose then
	    print(cnt, sample)
	    cnt = cnt + 1
	 else
	    print(sample)
	 end
      elseif ret < 0 then
	 print("reading mq failed")
	 os.exit(1)
      end
   end
end

local function sec2ts(sec)
   local ts_sec = math.floor(sec)
   local ts_nsec = (sec-ts_sec) * 1000^3
   return ts_sec, ts_nsec
end

--- write to the given mqueue
-- @param mqtab
-- @param preloads table of modules to preload
-- @param rate rate in Hz to write
local function write(mqtab, preloads, val, rate)

   local mq, sample = newmq(mqtab, preloads)
   local cnt = 0

   sample:set(val, false)

   if not rate then
      mq:write(sample)
      os.exit(0)
   else
      local s,ns = sec2ts(rate)
      while true do
	 if verbose then
	    mq:write(sample)
	    print(cnt, sample)
	    cnt = cnt + 1
	 else
	    mq:write(sample)
	 end
	 ubx.clock_mono_sleep(s,ns)
      end
   end
end

--- prog starts here
if #arg < 1 then
   usage(); os.exit(1)
end

local opttab = utils.proc_args(arg)

if opttab['-h'] then
   usage(); os.exit(0)
end

if opttab['-v'] then
   verbose=true
end

local preloads = {}
if opttab['-p'] then
   if not opttab['-p'][1] then
      print("-p requires an argument")
      os.exit(1)
   end
   preloads = utils.split(opttab['-p'][1], ',')
end

local mqs = mqueues_get()

if opttab[0][1] == "list" then
   if #mqs == 0 then
      print("no microblx mqueues found")
      os.exit(0)
   end
   nd = ubx.node_create("ubx-mq", { loglevel=8})
   ubx.load_module(nd, "stdtypes")
   ubx.load_module(nd, "mqueue")

   for _,m in ipairs(preloads) do
      ubx.load_module(nd, m)
   end

   local outtab = utils.map(
      function(t)
	 local typename
	 local typ = ubx.type_get_by_hashstr(nd, t.hashstr)
	 if typ ~= nil then
	    typename = ubx.safe_tostr(typ.name)
	 else
	    typename = "unknown"
	 end

	 return {t.mq_id, typename, ts(t.data_len), t.hashstr }
      end, mqs)

   utils.write_table(io.stdout, { "mq id", "type name", "array len", "type hash" }, outtab)

   os.exit(0)

elseif opttab[0][1] == "read" then

   local mq_id = opttab[0][2]

   if not mq_id then
      print("read expects an argument MQ-ID")
      os.exit(1)
   end

   for _,m in ipairs(mqs) do
      if m.mq_id == mq_id then read(m, preloads) end
   end

   print(fmt("invalid ubx queue id %s", mq_id))
   os.exit(1)

elseif opttab[0][1] == "write" then
   local rate
   local mq_id = opttab[0][2]

   if not mq_id then
      print("write expects arguments MQ-ID and VALUE")
      os.exit(1)
   end

   local str = opttab[0][3]
   local ok, val = utils.eval_sandbox("return "..str)

   if not ok then
      print(res)
      os.exit(1)
   end

   if opttab['-r'] then
      rate = opttab['-r'][1]
   end

   for _,m in ipairs(mqs) do
      if m.mq_id == mq_id then write(m, preloads, val, rate) end
   end

   print(fmt("invalid ubx queue id %s", mq_id))
   os.exit(1)

else
   print(fmt("unknown command %s", opttab[0][1]))
   os.exit(1)
end
